﻿//Part of NUGET Package: pmunin.SystemExtensions
#define ENTITY_FRAMEWORK
#define ASP_NET
#define ASP_NET_MVC
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Text.RegularExpressions;
#if ASP_NET
using System.Web;
#if ASP_NET_MVC
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Web.Routing;
#endif
#endif
using System.Globalization;
using System.Reflection;
#if ENTITY_FRAMEWORK
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
#endif

#if ASP_NET
namespace System.Web
{

    public static class HttpContextExtensions
    {

        public static Lazy<T> GetAndCacheLazyDuringRequest<T>(this HttpContext context, Func<T> valueInitializer, string cacheKey = null)
        {
            var fullKey = "AmbookCacheExtensions." + (cacheKey ?? "(no key)") + "." + typeof(T).FullName;
            var value = context.Items.GetAndCacheLazy<T>(valueInitializer, fullKey);
            return value;
        }

        public static T GetAndCacheDuringRequest<T>(this HttpContext context, Func<T> valueInitializer, string cacheKey = null)
        {
            return GetAndCacheLazyDuringRequest(context, valueInitializer, cacheKey).Value;
        }
    }

}
#endif

#if ASP_NET_MVC
namespace System.Web.Mvc
{
    public static class MvcExtensions
    {
        public static void UseNamespaceFallback(this Route route, bool useNamespaceFallback)
        {
            route.DataTokens["UseNamespaceFallback"] = useNamespaceFallback;
        }

        public static void MapRouteAreaFiles(this AreaRegistrationContext context, string routeName, string url, string physicalFilePath)
        {
            //context.Routes.MapPageRoute(routeName, urlFormat);
            context.Routes.Add(new Route(url, new StaticFileRouteHandler(physicalFilePath)));
            //context.Routes.
        }

        public class StaticFileRouteHandler : IRouteHandler
        {
            public string VirtualPath { get; set; }
            public StaticFileRouteHandler(string virtualPath)
            {
                VirtualPath = virtualPath;
            }

            public System.Web.IHttpHandler GetHttpHandler(RequestContext requestContext)
            {
                var vp = VirtualPath;
                foreach (var item in requestContext.RouteData.Values)
                {
                    vp = vp.Replace("{" + item.Key + "}", (item.Value ?? "").ToString());
                }
                HttpContext.Current.RewritePath(vp);

                var res = new DefaultHttpHandler();
                return res;

            }
        }

    }


    /// <summary>
    /// Extended model binder.
    /// Fixes issue with Checkbox list for string arrays: in standard if non of checkboxes were selected on UI, the model's property will not be binded, because field would not be passed in request
    /// </summary>
    public class ExtendedDefaultModelBinder : DefaultModelBinder
    {
        public static void Register()
        {
            var thisBinder = new ExtendedDefaultModelBinder();
            ModelBinders.Binders.DefaultBinder = thisBinder;
        }

        protected override ICustomTypeDescriptor GetTypeDescriptor(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            if (bindingContext.Model != null && bindingContext.ModelType == typeof(Object))
            {
                Type type = bindingContext.Model.GetType();
                return new AssociatedMetadataTypeTypeDescriptionProvider(type).GetTypeDescriptor(type);
            }
            else
            {
                return base.GetTypeDescriptor(controllerContext, bindingContext);
            }
        }

        public static bool DoValidationOnModelUpdated = false;
        public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            var res = base.BindModel(controllerContext, bindingContext);
            if (!DoValidationOnModelUpdated)
                bindingContext.ModelState.Clear();
            return res;
        }

        protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
        {
            var res = base.CreateModel(controllerContext, bindingContext, modelType);
            return res;
        }

        protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
        {
            base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
        }
        protected override void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value)
        {
            base.SetProperty(controllerContext, bindingContext, propertyDescriptor, value);
        }

        protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder)
        {
            var res = base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
            return res;
        }

        object DefaultEnumerable(Type t)
        {
            return null;
        }


    }

    /// <summary>
    /// Delegates model binding to controller, if controller implements interfaces nested in ControllerModelBinder class.
    /// It allows to let controller to instantiate Model, loading it from backend instead of creating empty object
    /// </summary>
    public class ControllerModelBinder : ExtendedDefaultModelBinder
    {
        /// <summary>
        /// If controller implements this interface then ControlModelBinder will delegate Model instantiation to the controller
        /// </summary>
        public interface ICanInstantiateModel
        {
            [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification = "This needs to be addressed!! Temporarily supressing the warning.")]
            bool TryCreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType, out object model);
        }

        public interface ISupportsOnModelBinded
        {
            void OnModelBinded(ControllerContext controllerContext, ModelBindingContext bindingContext, ref object model);
        }

        public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            var res = base.BindModel(controllerContext, bindingContext);

            var controller = controllerContext.Controller as ISupportsOnModelBinded;
            if (controller != null)
                controller.OnModelBinded(controllerContext, bindingContext, ref res);

            return res;
        }

        protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
        {
            var instantiator = controllerContext.Controller as ICanInstantiateModel;

            object res = null;
            if (instantiator != null && instantiator.TryCreateModel(controllerContext, bindingContext, modelType, out res)) return res;
            else return base.CreateModel(controllerContext, bindingContext, modelType);
        }
    }

    /// <summary>
    /// Delegates model binding to controller, if controller implements interfaces nested in ControllerModelBinder class.
    /// It allows to let controller to instantiate Model, loading it from backend instead of creating empty object
    /// </summary>
    public class ControllerModelBinderAttribute : CustomModelBinderAttribute
    {
        public override IModelBinder GetBinder()
        {
            return new ControllerModelBinder();
        }
    }

    /// <summary>
    /// Helps to convert Lambda expressions to string
    /// </summary>
    /// <typeparam name="TModel"></typeparam>
    public class ExpressionContext<TModel>// : IDisposable
    {
        //public void Dispose()
        //{ }
        public ExpressionContext(TModel model)
        {
        }

        public ExpressionContext<TSubModel> SubModel<TSubModel>(Expression<Func<TModel, TSubModel>> subModelPath)
        {
            return new ExpressionContext<TSubModel>(default(TSubModel));
        }

        public string Expression<TResult>(Expression<Func<TModel, TResult>> propertyExpression)
        {
            return System.Web.Mvc.ExpressionHelper.GetExpressionText(propertyExpression);
        }

        public static string Expression<TResult>(TModel model, Expression<Func<TModel, TResult>> propertyExpression)
        {
            return System.Web.Mvc.ExpressionHelper.GetExpressionText(propertyExpression);
        }

    }

}
#endif

namespace System
{
    /// <summary>
    /// Fire event when object disposes
    /// </summary>
    public interface INotifyOnDisposed
    {
        event EventHandler Disposed;
    }

#if ENTITY_FRAMEWORK
    public static class EntityFrameworkExtensions
    {
        public static void Clear_EF<T>(this ICollection<T> collection)
        {
            collection.ToList().ForEach(e => collection.Remove(e));
        }

        public static void RebuildCollection<T>(this ICollection<T> collection, IEnumerable<T> newValues)
        {
            collection.Clear_EF();
            if (newValues != null)
                newValues.ToList().ForEach(i => collection.Add(i));
        }
    }
#endif

    public static class NameValueCollectionExtensionsAdditional
    {
        public static bool Parse(this NameValueCollection collection, string key, bool defaultValue)
        {
            var res = defaultValue;
            if (bool.TryParse(collection[key], out res))
                return res;
            else
                return defaultValue;
        }
        public static int Parse(this NameValueCollection collection, string key, int defaultValue)
        {
            var res = defaultValue;
            if (int.TryParse(collection[key], out res))
                return res;
            else
                return defaultValue;
        }
    }

}


#if ENTITY_FRAMEWORK
namespace System
{

    public static class DbContextScalarQueryExtensions
    {

        public static object GetInternalContext(this DbContext context)
        {
            var provProp = context.GetType().GetProperty("InternalContext", BindingFlags.Instance | BindingFlags.NonPublic);
            return provProp.GetValue(context, null);
        }

        public static IQueryProvider GetRootObjectQueryProvider(this DbContext context)
        {
            var ctx = (context as IObjectContextAdapter).ObjectContext;

            var provProp = ctx.GetType().GetProperty("QueryProvider", BindingFlags.Instance | BindingFlags.NonPublic);
            var provider = provProp.GetValue(ctx, null) as IQueryProvider; //ObjectQueryProvider
            return provider;
        }

        static IQueryProvider CreateRootDbQueryProvider(DbContext context)
        {
            var dbQueryProviderType = Type.GetType("System.Data.Entity.Internal.Linq.DbQueryProvider, EntityFramework");
            var dbQueryProvider = Activator.CreateInstance(dbQueryProviderType, context.GetInternalContext(), context.GetRootObjectQueryProvider())
as IQueryProvider;
            return dbQueryProvider as IQueryProvider;
        }


        static Expression<Func<TResult>> RefactorExpression<TArgument, TResult>(Expression<Func<TArgument, TResult>> expOriginal, TArgument arg)
        {
            Expression<Func<TArgument>> expTemplate = () => arg;

            var arg2 = expTemplate.Body;
            var newBody = new Rewriter(expOriginal.Parameters[0], arg2).Visit(expOriginal.Body);
            return Expression.Lambda<Func<TResult>>(newBody);
        }

        class Rewriter : ExpressionVisitor
        {
            private readonly Expression candidate_;
            private readonly Expression replacement_;

            public Rewriter(Expression candidate, Expression replacement)
            {
                candidate_ = candidate;
                replacement_ = replacement;
            }

            public override Expression Visit(Expression node)
            {
                return node == candidate_ ? replacement_ : base.Visit(node);
            }
        }

        public static IQueryable<TResult> CreateScalarQuery<TDbContext, TResult>(this TDbContext context, Expression<Func<TDbContext, TResult>> expression)
            where TDbContext : DbContext
        {
            var resExp = RefactorExpression<TDbContext, TResult>(expression, (TDbContext)context);
            var realExp = Expression.Call(
                    method: GetMethodInfo(() => Queryable.Select<int, TResult>(null, (Expression<Func<int, TResult>>)null)),
                    arg0: Expression.Call(
                        method: GetMethodInfo(() => Queryable.AsQueryable<int>(null)),
                        arg0: Expression.NewArrayInit(typeof(int), Expression.Constant(1))),
                    arg1: Expression.Lambda(body: resExp.Body, parameters: new[] { Expression.Parameter(typeof(int)) }));

            return CreateRootDbQueryProvider(context).CreateQuery<TResult>(realExp);
        }

        public static IQueryable<TResult> SelectScalar<TElement, TResult>(this IEnumerable<TElement> q, Expression<Func<IQueryable<TElement>, TResult>> expression)
        {
            IQueryProvider queryProvider = (q as IQueryable).Provider;

            var expr = RefactorExpression(expression, (IQueryable<TElement>)q);

            var realExp = Expression.Call(
                    method: GetMethodInfo(() => Queryable.Select<int, TResult>(null, (Expression<Func<int, TResult>>)null)),
                    arg0: Expression.Call(
                        method: GetMethodInfo(() => Queryable.AsQueryable<int>(null)),
                        arg0: Expression.NewArrayInit(typeof(int), Expression.Constant(1))),
                    arg1: Expression.Lambda(body: expr.Body, parameters: new[] { Expression.Parameter(typeof(int)) }));

            return queryProvider.CreateQuery<TResult>(realExp);
        }

        static MethodInfo GetMethodInfo(Expression<Action> expression)
        {
            return ((MethodCallExpression)expression.Body).Method;
        }

    }


}
#endif


#if ASP_NET

public static partial class DictionaryExtensions
{
    /// <summary>
    /// Gets lazy value from dictionary or create it and put it in dictionary
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="dictionary"></param>
    /// <param name="valueInitializer"></param>
    /// <param name="key"></param>
    /// <returns></returns>
    public static Lazy<T> GetAndCacheLazy<T>(this System.Web.SessionState.HttpSessionState session, Func<T> valueInitializer, string key)
    {
        var fullKey = key;
        var currentValue = session[fullKey];
        var value = currentValue as Lazy<T>;

        if (value == null)
        {
            session[fullKey] = (value = new Lazy<T>(valueInitializer));
        }
        return value;
    }

    /// <summary>
    /// Try to gets value from dictionary, if value does not exist in dictionary it creates it and put it there by key
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="dictionary"></param>
    /// <param name="valueInitializer"></param>
    /// <param name="key"></param>
    /// <returns></returns>
    public static T GetAndCache<T>(this System.Web.SessionState.HttpSessionState session, Func<T> valueInitializer, string key)
    {
        var v = session.GetAndCacheLazy(valueInitializer: valueInitializer, key: key);
        return v.Value;
    }

}
#endif
